#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <functional>

#include <cerrno>
#include <cstring>

#include "mydll/Sock.hpp"
#include "mydll/Util.hpp"
#include "Epoller.hpp"
#include "mydll/Protocol.hpp"


namespace Epoll
{
    using namespace TCP;
    using namespace protocol;
    class Server;
    class Connection;

    class Connection //文件描述符缓冲区
    {
        using callback_t = std::function<void(Connection*)>; //事件处理函数 针对每一个fd设置独立的事件处理函数 避免服务器中繁杂的代码处理
    public:
        Connection(const int& fd,int events,const std::string& clientip,const uint16_t& clientport)
            :_fd(fd)
            ,_events(events)
            ,_clientip(clientip)
            ,_clientport(clientport)
        {}

        //注册方法
        void Register(callback_t recver,callback_t sender,callback_t excepter)
        {
            _recver = recver;
            _sender = sender;
            _excepter = excepter;
        }
    public:
        //sock信息
        int _fd;
        std::string _inbuffer;
        std::string _outbuffer;

        //事件处理函数
        callback_t _recver; //读
        callback_t _sender; //写
        callback_t _excepter; //异常

        //用户信息
        std::string _clientip;
        uint16_t _clientport;

        //记录事件
        int _events;

        //回指指针 用于设置读写
        Server* ser;
    };



    class Server
    {
        using func_t = std::function<void(Connection*,const Request&)>;
        const static int gnum = 64;
        const static int defaultport = 8088;
        const static int bsize = 1024; //读取缓冲区大小
    public:
        Server(const func_t& func,const uint16_t& port = defaultport)
            :_port(port)
            ,_ss(_port)
            ,_func(func)
        {}

        void Init()
        {
            _ss.Socket();
            _ss.Bind();
            _ss.Listen();

            //创建Epoll文件file
            _ep.Create();

            //将服务器fd设置进入epoll 使用默认ip和端口即可
            //AddConnection(_ss.GetSockFd(),EPOLLIN); //LT模式
            AddConnection(_ss.GetSockFd(),EPOLLIN | EPOLLET);  //ET模式 其读写接口必须为非阻塞
        }

        //事件派发器
        void Dispatcher()
        {

            int timeout = -1; //阻塞式监视
            while(true)
            {
                HandlerEvents(timeout);
            }
        }

        //读写设置
        bool EnableReadWrite(Connection *conn, bool readable, bool writeable)
        {
            conn->_events = (readable?EPOLLIN:0) | (writeable?EPOLLOUT:0) | EPOLLET; //根据参数 设置合适的标记
            return _ep.AddModEvent(conn->_fd,conn->_events,EPOLL_CTL_MOD);
        }

        ~Server() 
        {
            _ss.Close_Sockfd();
            _ep.Close();
        }

    protected:
        //扫描就绪事件 进行事件派发
        void HandlerEvents(int timeout)
        {
            int n = _ep.Wait(_revs,gnum,timeout);
            for(int i = 0;i<n;++i) //n<=0都不会进入循环
            {
                int fd = _revs[i].data.fd;
                uint32_t event = _revs[i].events;

                //我们不想过多的在很多地方调用_excepter函数 显得非常凌乱
                //我们将_excepter函数出现的情况 固定在_recver和_sender函数中 只有这两个函数触发_excepter
                if((event & EPOLLERR) || (event & EPOLLHUP)) event |= (EPOLLIN | EPOLLOUT); //标记读写事件 从中触发_excepter
                //_excepter函数将处理异常 并关闭sockfd


                //一旦触发异常 可能这个fd和链接资源_con就被删除了 我们使用[]访问 需要保证一定存在 需要检测安全性
                if((event & EPOLLIN) && (ConnIsExists(fd))) _con[fd]->_recver(_con[fd]); //读事件
                if((event & EPOLLOUT) && (ConnIsExists(fd))) _con[fd]->_sender(_con[fd]);// 写事件
                //if((event & EPOLLERR) || (event & EPOLLHUP)) _con[fd]->_excepter(_con[fd]);//异常事件 fd异常或对端关闭连接
            }
        }

        //添加一个新连接-连接管理器
        void AddConnection(const int& fd,const int& events,const std::string ip = "127.0.0.1",const uint16_t& port = defaultport)
        {
            //1.判断是否为ET模式 将文件描述符设置为非阻塞
            if(events & EPOLLET) Util::SetNonBlock(fd);
            //2.添加缓冲区
            Connection* conn = new Connection(fd,events,ip,port);
            if(fd == _ss.GetSockFd()) conn->Register(std::bind(&Server::Accept,this,std::placeholders::_1),nullptr,nullptr);

            else conn->Register(std::bind(&Server::Recver,this,std::placeholders::_1),
                                std::bind(&Server::Sender,this,std::placeholders::_1),
                                std::bind(&Server::Excepter,this,std::placeholders::_1));


            conn->ser = this; //设置回指指针
            //3.将服务器sockfd放入监听 并分配专属缓冲区
            _con[fd] = conn;
            //4.将文件描述符设置进epoll
            if(!_ep.AddModEvent(fd,events,EPOLL_CTL_ADD)) 
            {
                delete conn;
                _con.erase(fd);
                logMessage(Fatal,"AddConnection for AddEvent error! fd:%d",fd);
            }
            logMessage(Debug,"AddConnection success! fd:%d client:[%s|%d]",fd,ip.c_str(),port);

        }


        void Accept(Connection* conn)
        {
            //LT模式下读取一次结束
            //ET模式下循环读取
            do
            {
                std::string client_ip;
                uint16_t client_port;
                int err = 0;
                int client_sock = _ss.Accept(&client_ip,&client_port,&err);
                if(client_sock > 0)
                {
                    //服务器默认连接的文件描述符一开始是读取(向服务器写内容)
                    // if(!_ep.AddEvent(client_sock,EPOLLIN))
                    // {
                    //     close(client_sock); //加入失败则关闭连接
                    //     logMessage(Warning,"client AddEvent error!");
                    //     return;
                    // }
                    // Connection* conn = new Connection(_ss.GetSockFd());
                    // _con[client_sock] = conn;
                    
                    //AddConnection(client_sock,EPOLLIN,client_ip,client_port);
                    logMessage(Debug, "%s:%d 已经连上了服务器了", client_ip.c_str(), client_port);
                    AddConnection(client_sock,EPOLLIN | EPOLLET,client_ip,client_port);
                    continue;
                }
                else
                {
                    if(err == EAGAIN || err == EWOULDBLOCK) break;//读完了
                    else if(err == EINTR) continue;
                    else
                    {
                        logMessage(Warning,"errstring : %s, errcode: %d", strerror(err), err);
                        continue; //一个文件描述符出问题 不能影响后续读取
                    }
                }

            } while (conn->_events & EPOLLET);
            (void*)conn;
        }

        //处理读取
        void Recver(Connection* conn)
        {
            do //一轮对一个文件描述符进行完整读取
            {
                char buf[bsize] = {0}; //这里仍然无法保证一个应用层报文能读完整
                int rsize = recv(conn->_fd,buf,sizeof(buf)-1,0);
                if(rsize > 0)
                {
                    buf[rsize] = '\0';
                    _con[conn->_fd]->_inbuffer += buf;
                    
                    //序列化-反序列化
                    std::string requestStr;
                    int n = ParsePackage(_con[conn->_fd]->_inbuffer,&requestStr);
                    //进行文本分析 读到一个完整报文 n>0
                    if(n > 0) 
                    {
                        requestStr = RemoveHeader(requestStr,n);//去掉报头
                        Request req; //构建请求
                        req.Deserialize(requestStr); //反序列化 保证requestStr是一个完整的请求

                        _func(conn,req); //处理请求
                    }

                    logMessage(Debug, "inbuffer: %s, [%d]", conn->_inbuffer.c_str(), conn->_fd);
                    
                }
                else if(rsize == 0) //对端断开连接
                {
                    conn->_excepter(conn); //异常处理 关闭fd
                    break;
                }
                else 
                {
                    if(errno == EAGAIN || errno == EWOULDBLOCK) break;
                    else if(errno == EINTR) continue;
                    else //其他情况也是异常处理 关闭fd
                    {
                        conn->_excepter(conn); //异常处理 关闭fd
                        break;
                    }
                }
            }while(conn->_events & EPOLLET); //如果是ET工作模式则循环读取 非阻塞

        }

        //处理写入
        void Sender(Connection* conn)
        {
            do
            {
                int n = send(conn->_fd,conn->_outbuffer.c_str(),conn->_outbuffer.size(),0);
                if(n > 0)
                {
                    conn->_outbuffer.erase(0,n); //删除已经发送的n个字符
                    if(conn->_outbuffer.empty()) 
                    {
                        EnableReadWrite(conn,true,false); //如果全部发送 则关闭写入
                        break; //停止写入
                    }
                    else EnableReadWrite(conn,true,true); //如果没有发完 则继续发送
                }
                else
                {
                    if(errno == EAGAIN || errno == EWOULDBLOCK) break; //如果写入缓冲区已满 或其他原因无法发送 则暂停发送
                    else if(errno == EINTR) continue; //如果因为信号原因暂停 则继续
                    else //异常情况
                    {
                        conn->_excepter(conn); 
                        break;
                    }
                }
            }while(conn->_events & EPOLLET); //如果是ET工作模式则循环写入 非阻塞
        }

        //处理异常
        void Excepter(Connection* conn)
        {

        }

        bool ConnIsExists(int fd)
        {
            return _con.find(fd) != _con.end();
        }

    private:
        uint16_t _port;
        SockServer _ss;
        Epoller _ep;
        struct epoll_event _revs[gnum]; //事件管理数组 接受每一个fd所就绪的事件
        func_t _func; //任务处理函数
        std::unordered_map<int,Connection*> _con; //建立文件描述符和缓冲区的映射
    };
}